package net.xdclass.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.entity.Coupon;
import net.xdclass.entity.CouponRecord;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.CouponCategoryEnum;
import net.xdclass.enums.CouponPublishEnum;
import net.xdclass.enums.CouponStateEnum;
import net.xdclass.exception.BizException;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.mapper.CouponMapper;
import net.xdclass.mapper.CouponRecordMapper;
import net.xdclass.model.LoginUser;
import net.xdclass.request.NewUserRequest;
import net.xdclass.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.xdclass.utils.CommonUtil;
import net.xdclass.utils.JsonData;
import net.xdclass.vo.CouponVO;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.ParameterResolutionDelegate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author lixin
 * @since 2024-08-23
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Autowired
    private CouponMapper couponMapper;

    @Autowired
    private CouponRecordMapper couponRecordMapper;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 分页查询优惠卷
     * @param page
     * @param size
     * @return
     */
    @Override
    public Map<String, Object> pageCouponActivity(int page, int size) {
        Page<Coupon> pageInfo = new Page<>(page, size);

        Page<Coupon> couponPage = couponMapper.selectPage(pageInfo, new QueryWrapper<Coupon>()
                .eq("publish", CouponPublishEnum.PUBLISH)
                .eq("category", CouponCategoryEnum.PROMOTION)
                .orderByDesc("create_time"));

        Map<String, Object> pageMap = new HashMap<>(3);
        //总条数
        pageMap.put("total_record", couponPage.getTotal());
        //总页数
        pageMap.put("total_page", couponPage.getPages());
        //当前页内容
        pageMap.put("current_data", couponPage.getRecords().stream().map(obj -> beanProcess(obj)).collect(Collectors.toList()));

        return pageMap;
    }

    /**
     * 领取优惠卷接口
     * 1.优惠卷是否存在
     * 2.校验优惠卷是否可以领取： 时间、库存、超过限制
     * 3.扣减库存
     * 4.保存领卷记录
     * 5.开启事务
     * @param couponId
     * @param category
     * @return
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    @Override
    public JsonData addCoupon(long couponId, CouponCategoryEnum category) {

        LoginUser loginUser = LoginInterceptor.threadLocal.get();

        String lockKey = "lock:coupon:" + couponId;

        //多个线程进入会阻塞，等待锁释放
        RLock rLock = redissonClient.getLock(lockKey);
        rLock.lock();
        log.info("领券接口加锁成功: {}", Thread.currentThread().getId());


        try{
                //执行业务逻辑
                Coupon coupon = couponMapper.selectOne(new QueryWrapper<Coupon>().eq("id", couponId)
                        .eq("category", category.name()));

                if (coupon == null){
                    throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
                }

                //优惠卷是否可以领取
                this.checkCoupon(coupon, loginUser.getId());

                //构建领卷记录
                CouponRecord couponRecord = new CouponRecord();
                BeanUtils.copyProperties(coupon, couponRecord);
                couponRecord.setCreateTime(LocalDateTime.now());
                couponRecord.setUseState(CouponStateEnum.NEW.name());
                couponRecord.setUserId(loginUser.getId());
                couponRecord.setUserName(loginUser.getName());
                couponRecord.setCouponId(couponId);
                couponRecord.setId(null);

                //扣减库存
                int rows = couponMapper.reduceStock(couponId);

                if (rows == 1){
                    //新增扣减成功才保存
                    couponRecordMapper.insert(couponRecord);
                }else {
                    log.warn("发放优惠卷失败:{}, 用户:{}", coupon, loginUser);
                    throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
                }

            }finally {
            rLock.unlock();
            log.info("解锁成功 ");
            }

        return JsonData.buildSuccess();
    }

    /**
     * 新用户注册发放优惠券
     * 用户微服务调用的时候，没传递token
     * 本地直接调用发放优惠券的方法，需要构造一个登录用户存储到threadLocal里
     * @param newUserRequest
     * @return
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    @Override
    public JsonData initNewUserCoupon(NewUserRequest newUserRequest) {
        LoginUser loginUser = new LoginUser();
        loginUser.setId(newUserRequest.getUserId());
        loginUser.setName(newUserRequest.getName());
        LoginInterceptor.threadLocal.set(loginUser);

        //查询新用户有哪些优惠券
        List<Coupon> couponList = couponMapper.selectList(new QueryWrapper<Coupon>()
                .eq("category", CouponCategoryEnum.NEW_USER.name()));

        for (Coupon coupon : couponList) {
            //幂等操作，需要加锁
            this.addCoupon(coupon.getId(), CouponCategoryEnum.NEW_USER);
        }

//        int a = 1 / 0;

        return JsonData.buildSuccess();
    }

    /**
     * 校验优惠卷是否可以领取
     * 1.库存是否足够
     * 2.判断是否是发布状态
     * 3.是否在领取时间范围
     * 4.用户是否超过限制
     * @param coupon
     * @param userId
     */
    private void checkCoupon(Coupon coupon, Long userId) {

        if (coupon.getStock() <= 0){
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }

        //判断是否是发布状态
        if (coupon.getPublish() .equals(CouponPublishEnum.PUBLISH)){
            throw new BizException(BizCodeEnum.COUPON_GET_FAIL);
        }

        long time = CommonUtil.getCurrentTimestamp();
        long start = coupon.getStartTime().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long end = coupon.getEndTime().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (time < start || time > end){
            //不在领取范围内
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }

        Integer recordNum = couponRecordMapper.selectCount(new QueryWrapper<CouponRecord>().eq("coupon_id", coupon.getId())
                .eq("user_id", userId));
        if(recordNum >= coupon.getUserLimit()){
            //用户超过限制次数
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
        
    }

    private CouponVO beanProcess(Coupon coupon) {
        CouponVO couponVO = new CouponVO();
        BeanUtils.copyProperties(coupon, couponVO);
        return couponVO;
    }
}
